/*-------------------------------------------------------------------------
 *
 * xlogarchive.c
 *		归档 WAL 文件和从归档中恢复的函数。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/access/transam/xlogarchive.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"

/*
 * 尝试从离线归档存储中检索指定文件。
 * 如果成功，则将“path”填充为其完整路径（注意这将是一个不符合
 * 正常命名规范的临时文件名），并返回true。
 *
 * 如果不成功，则将“path”填充为正常在线文件的名称
 * （该文件可能存在也可能不存在，但我们将尝试使用它），并返回
 * false。
 *
 * 对于固定大小文件，调用者可以传递预期大小作为
 * 成功恢复的额外交叉检查。如果文件大小未知，则设置 expectedSize = 0。
 *
 * 当 'cleanupEnabled' 为 false 时，避免删除归档中的任何旧 WAL 段。
 * 这在获取初始检查点记录时使用，当我们尚不确定需要多远的 WAL。
 */
bool RestoreArchivedFile(char *fc_path, const char *fc_xlogfname,
					const char *fc_recovername, off_t fc_expectedSize,
					bool fc_cleanupEnabled)
{
	char		fc_xlogpath[MAXPGPATH];
	char	   *fc_xlogRestoreCmd;
	char		fc_lastRestartPointFname[MAXPGPATH];
	int			fc_rc;
	struct stat fc_stat_buf;
	XLogSegNo	fc_restartSegNo;
	XLogRecPtr	fc_restartRedoPtr;
	TimeLineID	fc_restartTli;

	/*
	 * 在非归档恢复时忽略 restore_command（意味着我们处于
	 * 崩溃恢复中）。
	 */
	if (!ArchiveRecoveryRequested)
		goto not_available;

	/* 在备用模式下，可能不会提供 restore_command */
	if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
		goto not_available;

	/*
	 * 在进行归档恢复时，即使在 XLOGDIR 中存在同名文件，我们也始终优先使用归档日志文件。
	 * 原因是 XLOGDIR 中的文件可能是旧的、未填满或部分填满的版本，
	 * 该版本是作为备份 $PGDATA 的一部分被复制和恢复的。
	 *
	 * 我们可以通过检查本地副本的 lastchange 时间戳与归档副本的比较略微优化此过程，
	 * 但我们没有 API 来执行此操作，也不能保证在复制到归档时
	 * lastchange 时间戳被正确保留。我们的目标是健壮性，
	 * 所以我们选择不这样做。
	 *
	 * 然而，如果我们无法从归档获取日志文件，我们将尝试
	 * 使用 XLOGDIR 文件（如果存在）。这样我们就可以利用
	 * 尚未传输到归档的日志段。
	 *
	 * 请注意，当我们从归档复制时实际上不会覆盖任何文件，
	 * 因为 restore_command 可能会无意中恢复不适当的 xlogs，或者它们可能会损坏，
	 * 因此我们可能希望稍后回退到当前 XLOGDIR 中剩余的段。
	 * 从归档复制的文件名始终相同，以确保在长时间恢复期间不会耗尽磁盘空间。
	 */
	snprintf(fc_xlogpath, MAXPGPATH, XLOGDIR "/%s", fc_recovername);

	/*
	 * 确保不存在名为 recovername 的现有文件。
	 */
	if (stat(fc_xlogpath, &fc_stat_buf) != 0)
	{
		if (errno != ENOENT)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m",
							fc_xlogpath)));
	}
	else
	{
		if (unlink(fc_xlogpath) != 0)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m",
							fc_xlogpath)));
	}

	/*
	 * 计算用于日志传输复制的归档文件截止点。所有早于此点的文件可以从
	 * 归档中删除，但没有强制要求这样做。
	 *
	 * 如果未启用清理，则使用InvalidXLogRecPtr的文件名初始化，
	 * 这将防止从归档中删除任何WAL文件，因为WAL文件名的字母排序特性。
	 *
	 * 一旦我们成功定位了恢复开始时的检查点重做指针，我们将不再请求
	 * 在最后一次重启点之前的文件。当重做开始时，我们知道我们已经成功
	 * 定位它，因此无需额外的状态标志来标示我们可以开始从归档中删除
	 * WAL文件的点。
	 */
	if (fc_cleanupEnabled)
	{
		GetOldestRestartPoint(&fc_restartRedoPtr, &fc_restartTli);
		XLByteToSeg(fc_restartRedoPtr, fc_restartSegNo, wal_segment_size);
		XLogFileName(fc_lastRestartPointFname, fc_restartTli, fc_restartSegNo,
					 wal_segment_size);
		/* 我们不应该需要早于最后重启点的任何内容 */
		Assert(strcmp(fc_lastRestartPointFname, fc_xlogfname) <= 0);
	}
	else
		XLogFileName(fc_lastRestartPointFname, 0, 0L, wal_segment_size);

	/* 构建要执行的恢复命令 */
	fc_xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
										 fc_xlogpath, fc_xlogfname,
										 fc_lastRestartPointFname);
	if (fc_xlogRestoreCmd == NULL)
		elog(ERROR, "could not build restore command \"%s\"",
			 recoveryRestoreCommand);

	ereport(DEBUG3,
			(errmsg_internal("executing restore command \"%s\"",
							 fc_xlogRestoreCmd)));

	pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);

	/*
	 * PreRestoreCommand()通知启动进程的SIGTERM处理程序
	 * 它应该立即调用proc_exit()。这是在system()调用期间
	 * 进行的，因为在执行时没有好的方法退出。由于我们可能在信号处理
	 * 程序中调用proc_exit()，因此最好在
	 * PreRestoreCommand()/PostRestoreCommand()部分之前或之后
	 * 放置任何额外的逻辑。
	 */
	PreRestoreCommand();

	/*
	 * 从归档存储复制xlog到XLOGDIR
	 */
	fc_rc = system(fc_xlogRestoreCmd);

	PostRestoreCommand();

	pgstat_report_wait_end();
	pfree(fc_xlogRestoreCmd);

	if (fc_rc == 0)
	{
		/*
		 * 命令显然成功了，但让我们确保文件现在确实在那里并且具有正确的大小。
		 */
		if (stat(fc_xlogpath, &fc_stat_buf) == 0)
		{
			if (fc_expectedSize > 0 && fc_stat_buf.st_size != fc_expectedSize)
			{
				int			fc_elevel;

				/*
				 * 如果我们在备用模式下找到一个部分文件，我们假设
				 * 这是因为它正被复制到归档中，然后继续尝试。
				 *
				 * 否则将错误大小的文件视为FATAL，以确保DBA会注意到它，
				 * 但这是否太强了？我们可以尝试用本地副本继续，但
				 * 问题是可能没有副本，我们可能错误地得出结论
				 * 我们已经到达WAL的末尾，恢复完成了...
				 */
				if (StandbyMode && fc_stat_buf.st_size < fc_expectedSize)
					fc_elevel = DEBUG1;
				else
					fc_elevel = FATAL;
				ereport(fc_elevel,
						(errmsg("archive file \"%s\" has wrong size: %lld instead of %lld",
								fc_xlogfname,
								(long long int) fc_stat_buf.st_size,
								(long long int) fc_expectedSize)));
				return false;
			}
			else
			{
				ereport(LOG,
						(errmsg("restored log file \"%s\" from archive",
								fc_xlogfname)));
				strcpy(fc_path, fc_xlogpath);
				return true;
			}
		}
		else
		{
			/* stat失败 */
			int			fc_elevel = (errno == ENOENT) ? LOG : FATAL;

			ereport(fc_elevel,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_xlogpath),
					 errdetail("restore_command returned a zero exit status, but stat() failed.")));
		}
	}

	/*
	 * 请记住，我们会回滚直到恢复失败，因此这里的失败只是过程的一部分……这使得很难确定恢复失败是因为没有可恢复的归档文件，还是因为管理员错误地指定了恢复程序。我们必须假设前者。
	 *
	 * 然而，如果失败是由于某种信号引起的，最好停止并中止恢复。（如果我们在这里“返回false”，上层将假定恢复已完成并启动数据库！）在子进程收到SIGINT和SIGQUIT时中止是至关重要的，因为根据规范，system()在等待时忽略SIGINT和SIGQUIT；如果我们看到其中之一，这表明我们也应该收到它。
	 *
	 * 在SIGTERM时，假设我们收到了快速关机请求，并且干净退出。我们是否首先接收到SIGTERM或子进程的接收完全是偶然。如果我们首先收到了信号处理程序将调用proc_exit，否则我们在这里执行。如果我们或子进程因快速关机请求以外的任何原因收到SIGTERM，postmaster将会在看到我们意外退出时立即关机。
	 *
	 * 我们也将如“未找到命令”等硬壳错误视为致命错误。
	 */
	if (wait_result_is_signal(fc_rc, SIGTERM))
		proc_exit(1);

	ereport(wait_result_is_any_signal(fc_rc, true) ? FATAL : DEBUG2,
			(errmsg("could not restore file \"%s\" from archive: %s",
					fc_xlogfname, wait_result_to_str(fc_rc))));

not_available:

	/*
	 * 如果归档文件不可用，XLOGDIR中可能仍有该文件的一个版本，因此返回该文件名以打开。
	 *
	 * 在许多恢复场景中，我们预计这也会失败，但如果如此，那就意味着我们已经达到了WAL的尽头。
	 */
	snprintf(fc_path, MAXPGPATH, XLOGDIR "/%s", fc_xlogfname);
	return false;
}

/*
 * 尝试在恢复过程中执行外部 shell 命令。
 *
 * 'command'是要执行的 shell 命令，'commandName'是描述该命令的可读名称，记录在日志中。如果 'failOnSignal' 为真且命令被信号杀死，则抛出 FATAL 错误。否则发出 WARNING。
 *
 * 这目前用于 recovery_end_command 和 archive_cleanup_command。
 */
void ExecuteRecoveryCommand(const char *fc_command, const char *fc_commandName,
					   bool fc_failOnSignal, uint32 fc_wait_event_info)
{
	char		fc_xlogRecoveryCmd[MAXPGPATH];
	char		fc_lastRestartPointFname[MAXPGPATH];
	char	   *fc_dp;
	char	   *fc_endp;
	const char *fc_sp;
	int			fc_rc;
	XLogSegNo	fc_restartSegNo;
	XLogRecPtr	fc_restartRedoPtr;
	TimeLineID	fc_restartTli;

	Assert(fc_command && fc_commandName);

	/*
	 * 计算在日志传送复制期间使用的归档文件截止点。所有早于此点的文件可以从归档中删除，尽管没有要求这样做。
	 */
	GetOldestRestartPoint(&fc_restartRedoPtr, &fc_restartTli);
	XLByteToSeg(fc_restartRedoPtr, fc_restartSegNo, wal_segment_size);
	XLogFileName(fc_lastRestartPointFname, fc_restartTli, fc_restartSegNo,
				 wal_segment_size);

	/*
	 * 构造要执行的命令
	 */
	fc_dp = fc_xlogRecoveryCmd;
	fc_endp = fc_xlogRecoveryCmd + MAXPGPATH - 1;
	*fc_endp = '\0';

	for (fc_sp = fc_command; *fc_sp; fc_sp++)
	{
		if (*fc_sp == '%')
		{
			switch (fc_sp[1])
			{
				case 'r':
					/* %r：最后一个重启点的文件名 */
					fc_sp++;
					strlcpy(fc_dp, fc_lastRestartPointFname, fc_endp - fc_dp);
					fc_dp += strlen(fc_dp);
					break;
				case '%':
					/* 将%%转换为一个% */
					fc_sp++;
					if (fc_dp < fc_endp)
						*fc_dp++ = *fc_sp;
					break;
				default:
					/* 否则将%视为普通字符 */
					if (fc_dp < fc_endp)
						*fc_dp++ = *fc_sp;
					break;
			}
		}
		else
		{
			if (fc_dp < fc_endp)
				*fc_dp++ = *fc_sp;
		}
	}
	*fc_dp = '\0';

	ereport(DEBUG3,
			(errmsg_internal("executing %s \"%s\"", fc_commandName, fc_command)));

	/*
	 * 执行构造的命令
	 */
	pgstat_report_wait_start(fc_wait_event_info);
	fc_rc = system(fc_xlogRecoveryCmd);
	pgstat_report_wait_end();

	if (fc_rc != 0)
	{
		/*
		 * 如果失败是由于任何信号引起的，最好停止并中止恢复。请参见RestoreArchivedFile()中的注释。
		 */
		ereport((fc_failOnSignal && wait_result_is_any_signal(fc_rc, true)) ? FATAL : WARNING,
		/*------
		   translator: 第一个 %s 代表像 "recovery_end_command" 这样的 postgresql.conf 参数名，第二个是该参数的值，第三个是已经翻译的错误信息。 */
				(errmsg("%s \"%s\": %s", fc_commandName,
						fc_command, wait_result_to_str(fc_rc))));
	}
}


/*
 * 从归档中恢复了一个文件，使用了临时文件名（路径），
 * 现在我们想保留它。将其重命名为 pg_wal 中的永久文件名
 * (xlogfname)，替换任何具有相同名称的现有文件。
 */
void KeepFileRestoredFromArchive(const char *fc_path, const char *fc_xlogfname)
{
	char		fc_xlogfpath[MAXPGPATH];
	bool		fc_reload = false;
	struct stat fc_statbuf;

	snprintf(fc_xlogfpath, MAXPGPATH, XLOGDIR "/%s", fc_xlogfname);

	if (stat(fc_xlogfpath, &fc_statbuf) == 0)
	{
		char		fc_oldpath[MAXPGPATH];

#ifdef WIN32
		static unsigned int deletedcounter = 1;

		/*
		 * 在 Windows 上，如果另一个进程（例如 walsender 进程）以
		 * FILE_SHARE_DELETE 模式打开该文件，解除链接将成功，但
		 * 该文件在最后一个句柄关闭之前仍然会出现在目录列表中，
		 * 我们无法在那之前将新文件重命名为其位置。为避免该问题，
		 * 首先将旧文件重命名为临时名称。使用计数器创建唯一的文件名，
		 * 因为同一个文件可能会从归档中恢复多次，且 walsender
		 * 仍可能持有其旧的删除版本。
		 */
		snprintf(oldpath, MAXPGPATH, "%s.deleted%u",
				 xlogfpath, deletedcounter++);
		if (rename(xlogfpath, oldpath) != 0)
		{
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not rename file \"%s\" to \"%s\": %m",
							xlogfpath, oldpath)));
		}
#else
		/* 相同大小的缓冲区，因此这不会截断 */
		strlcpy(fc_oldpath, fc_xlogfpath, MAXPGPATH);
#endif
		if (unlink(fc_oldpath) != 0)
			ereport(FATAL,
					(errcode_for_file_access(),
					 errmsg("could not remove file \"%s\": %m",
							fc_xlogfpath)));
		fc_reload = true;
	}

	durable_rename(fc_path, fc_xlogfpath, ERROR);

	/*
	 * 强制创建 .done 文件，以防止恢复的段在之后被
	 * 重新归档。
	 */
	if (XLogArchiveMode != ARCHIVE_MODE_ALWAYS)
		XLogArchiveForceDone(fc_xlogfname);
	else
		XLogArchiveNotify(fc_xlogfname);

	/*
	 * 如果现有文件被替换，由于 walsenders 可能已经打开它，
	 * 请求它们重新加载当前打开的段。这仅对 WAL 段是必需的，
	 * walsenders 不会持有其他文件，但这样做也无妨，
	 * 我们不知道这里涉及到什么类型的文件。
	 */
	if (fc_reload)
		WalSndRqstFileReload();

	/*
	 * 向 walsender 通知新 WAL 已到达。同样，如果我们恢复的是
	 * 其他类型的文件，这不是必需的，但也不会造成损害。
	 */
	WalSndWakeup();
}

/*
 * XLogArchiveNotify
 *
 * 创建归档通知文件
 *
 * 通知文件的名称是将被归档器提取的消息，
 * 例如，我们写入 0000000100000001000000C6.ready，
 * 然后归档器知道要归档 XLOGDIR/0000000100000001000000C6，
 * 完成后，将其重命名为 0000000100000001000000C6.done。
 */
void XLogArchiveNotify(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];
	FILE	   *fc_fd;

	/* 插入一个名为 <XLOG>.ready 的空文件 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	fc_fd = AllocateFile(fc_archiveStatusPath, "w");
	if (fc_fd == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not create archive status file \"%s\": %m",
						fc_archiveStatusPath)));
		return;
	}
	if (FreeFile(fc_fd))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write archive status file \"%s\": %m",
						fc_archiveStatusPath)));
		return;
	}

	/*
	 * 时间线历史文件被赋予最高的归档优先级，以降低
	 * 提升的备用选取已在使用的时间线的机会。
	 * 然而，归档器通常尝试从每次扫描归档状态目录中收集
	 * 多个文件进行归档，这意味着新创建的时间线历史文件
	 * 可能会在一段时间内未被归档。为确保归档器能尽快
	 * 拾取时间线历史文件，我们强制归档器在下次查找
	 * 要归档的文件时扫描归档状态目录。
	 */
	if (IsTLHistoryFileName(fc_xlog))
		PgArchForceDirScan();

	/* 通知归档器有事情要做 */
	if (IsUnderPostmaster)
		PgArchWakeup();
}

/*
 * 使用文件名的段编号表示法进行通知的便利例程
 */
void XLogArchiveNotifySeg(XLogSegNo fc_segno, TimeLineID fc_tli)
{
	char		fc_xlog[MAXFNAMELEN];

	Assert(fc_tli != 0);

	XLogFileName(fc_xlog, fc_tli, fc_segno, wal_segment_size);
	XLogArchiveNotify(fc_xlog);
}

/*
 * XLogArchiveForceDone
 *
 * 强制发出通知，表明 XLOG 段文件已成功归档，
 * 通过创建 <XLOG>.done，无论 <XLOG>.ready
 * 是否存在。
 */
void XLogArchiveForceDone(const char *fc_xlog)
{
	char		fc_archiveReady[MAXPGPATH];
	char		fc_archiveDone[MAXPGPATH];
	struct stat fc_stat_buf;
	FILE	   *fc_fd;

	/* 如果已知已完成则退出 */
	StatusFilePath(fc_archiveDone, fc_xlog, ".done");
	if (stat(fc_archiveDone, &fc_stat_buf) == 0)
		return;

	/* 如果 .ready 存在，则将其重命名为 .done */
	StatusFilePath(fc_archiveReady, fc_xlog, ".ready");
	if (stat(fc_archiveReady, &fc_stat_buf) == 0)
	{
		(void) durable_rename(fc_archiveReady, fc_archiveDone, WARNING);
		return;
	}

	/* 插入一个名为 <XLOG>.done 的空文件 */
	fc_fd = AllocateFile(fc_archiveDone, "w");
	if (fc_fd == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not create archive status file \"%s\": %m",
						fc_archiveDone)));
		return;
	}
	if (FreeFile(fc_fd))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write archive status file \"%s\": %m",
						fc_archiveDone)));
		return;
	}
}

/*
 * XLogArchiveCheckDone
 *
 * 当我们准备删除或回收一个旧的 XLOG 段文件或备份历史文件时调用此函数。如果可以删除，则返回 true。如果不可以删除，请确保 .ready 文件存在，并返回 false。
 *
 * 如果 <XLOG>.done 存在，则返回 true；如果 <XLOG>.ready 存在，则返回 false；否则创建 <XLOG>.ready 并返回 false。
 *
 * 我们这样做的原因是，如果创建 <XLOG>.ready 的初始尝试失败，我们将在后续的检查点期间重试。
 */
bool XLogArchiveCheckDone(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];
	struct stat fc_stat_buf;

	/* 如果 archive_mode 是 "off"，则文件始终可以删除。 */
	if (!XLogArchivingActive())
		return true;

	/*
	 * 在归档恢复期间，如果 archive_mode 不是 "always"，则文件可删除。
	 */
	if (!XLogArchivingAlways() &&
		GetRecoveryState() == RECOVERY_STATE_ARCHIVE)
		return true;

	/*
	 * 在逻辑的这个时间点，注意我们要么是将 archive_mode 设置为 "on" 或 "always" 的主节点，要么是将 archive_mode 设置为 "always" 的备节点。
	 */

	/* 首先检查 .done --- 这意味着归档器已经完成 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	/* 检查 .ready --- 这意味着归档器仍在处理它 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return false;

	/* 竞争条件 --- 归档器可能刚刚完成，因此重新检查 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	/* 重试创建 .ready 文件 */
	XLogArchiveNotify(fc_xlog);
	return false;
}

/*
 * XLogArchiveIsBusy
 *
 * 检查一个 XLOG 段文件是否仍未归档。
 * 这几乎但不完全是 XLogArchiveCheckDone 的反向操作：首先，我们不负责重新创建 .ready 文件，其次，我们应该考虑如果文件已经消失，那么它就不再忙碌。(这个检查是处理竞争条件的，需要处理检查点已经删除了不再需要的文件。)
 */
bool XLogArchiveIsBusy(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];
	struct stat fc_stat_buf;

	/* 首先检查 .done --- 这意味着归档器已经完成 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return false;

	/* 检查 .ready --- 这意味着归档器仍在处理它 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	/* 竞争条件 --- 归档器可能刚刚完成，因此重新检查 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return false;

	/*
	 * 检查 WAL 文件是否已被检查点删除，这意味着它已经被归档，并解释了我们为什么看不到其状态文件。
	 */
	snprintf(fc_archiveStatusPath, MAXPGPATH, XLOGDIR "/%s", fc_xlog);
	if (stat(fc_archiveStatusPath, &fc_stat_buf) != 0 &&
		errno == ENOENT)
		return false;

	return true;
}

/*
 * XLogArchiveIsReadyOrDone
 *
 * 检查一个 XLOG 段文件是否有 .ready 或 .done 文件。
 * 这与 XLogArchiveIsBusy() 类似，但如果文件已经归档或即将归档，则返回 true。
 *
 * 目前仅在恢复期间使用。在正常操作期间，这可能会发生竞争：文件在我们检查它时可能被删除或标记为 .ready，或者在我们返回后立即被标记。
 */
bool XLogArchiveIsReadyOrDone(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];
	struct stat fc_stat_buf;

	/* 首先检查 .done --- 这意味着归档器已经完成 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	/* 检查 .ready --- 这意味着归档器仍在处理它 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	/* 竞争条件 --- 归档器可能刚刚完成，因此重新检查 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	return false;
}

/*
 * XLogArchiveIsReady
 *
 * 检查一个 XLOG 段文件是否有归档通知 (.ready) 文件。
 */
bool XLogArchiveIsReady(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];
	struct stat fc_stat_buf;

	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	if (stat(fc_archiveStatusPath, &fc_stat_buf) == 0)
		return true;

	return false;
}

/*
 * XLogArchiveCleanup
 *
 * 清理特定 xlog 段的归档通知文件。
 */
void XLogArchiveCleanup(const char *fc_xlog)
{
	char		fc_archiveStatusPath[MAXPGPATH];

	/* 移除 .done 文件 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".done");
	unlink(fc_archiveStatusPath);
	/* 我们应该对失败进行投诉吗？ */

	/* 如果存在，则移除 .ready 文件 --- 通常它不应该存在 */
	StatusFilePath(fc_archiveStatusPath, fc_xlog, ".ready");
	unlink(fc_archiveStatusPath);
	/* 我们应该对失败进行投诉吗？ */
}
